package dnl.toolbox.statistics;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import dnl.utils.text.table.TextTable;
import dnl.utils.text.table.TextTableModel;
import dnl.utils.text.table.TextTreeTable;

/**
 * 
 * @author Daniel Orr
 * 
 */
public class Statistics implements Serializable {

	private static final long serialVersionUID = 1L;
	
	private List<List<Object>> statisticsData = new ArrayList<List<Object>>();

	private List<String> columnTypes = new ArrayList<String>();
	private List<String> columnTitles = new ArrayList<String>();

	private boolean isHierarchical;
	
	public Statistics() {
	}

	/**
	 * 
	 * @param columnTypes
	 * @param columnTitles
	 */
	public Statistics(Class<?>[] columnTypes, String[] columnTitles) {
		this.columnTitles = Arrays.asList(columnTitles);
		this.setColumnTypes(columnTypes);
	}

	/**
	 * 
	 * @return
	 */
	public List<List<Object>> getStatisticsData() {
		return statisticsData;
	}

	/**
	 * 
	 * @param statisticsData
	 */
	public void setStatisticsData(List<List<Object>> statisticsData) {
		this.statisticsData = statisticsData;
	}

	public boolean isHierarchical() {
		return isHierarchical;
	}

	public void setHierarchical(boolean isHierarchical) {
		this.isHierarchical = isHierarchical;
	}


	/**
	 * 
	 * @param columnTypes
	 */
	public void setColumnTypes(Class<?>... columnTypes) {
		if (columnTitles != null && columnTitles.size() != columnTypes.length) {
			throw new IllegalArgumentException(
					"Number of column titles and column types does not match.");
		}
		List<String> types = new ArrayList<String>(columnTypes.length);
		for (Class<?> clazz : columnTypes) {
			types.add(clazz.getName());
		}
		this.columnTypes = types;
	}

	public List<String> getColumnTypes() {
		return columnTypes;
	}

	public void setColumnTypes(List<String> columnTypes) {
		this.columnTypes = columnTypes;
	}

	public void setColumnTitles(List<String> columnTitles) {
		this.columnTitles = columnTitles;
	}
	
	/**
	 * 
	 * @return
	 */
	public List<String> getColumnTitles() {
		return columnTitles;
	}

	/**
	 * 
	 * @param columnTitles
	 */
	public void setColumnTitles(String... columnTitles) {
		this.columnTitles = Arrays.asList(columnTitles);
	}

	/**
	 * 
	 * @param column
	 * @return
	 */
	public Object getTotal(int column) {
		if (column > columnTypes.size()) {
			throw new IllegalArgumentException("No column at index " + column);
		}
		BigDecimal total = new BigDecimal(0);
		for (List<Object> objects : statisticsData) {
			Number number = (Number) objects.get(column);
			BigDecimal num = new BigDecimal(number.toString());
			total = total.add(num);
		}
		return total;
	}

	/**
	 * 
	 * @param column
	 * @return
	 */
	public boolean isNumeric(int column) {
		if (column > columnTypes.size()) {
			throw new IllegalArgumentException("No column at index " + column);
		}
		
		return Number.class.isAssignableFrom(getColumnType(column));
	}

	private Class<?> getColumnType(int column) {
		try {
			Class<?> clazz = Class.forName(columnTypes.get(column));
			return clazz;
		} catch (ClassNotFoundException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * 
	 * @param data
	 */
	public void addStatisticsData(Object... data) {
		addStatisticsData(Arrays.asList(data));
	}

	public void addStatisticsData(List<Object> data) {
		for (int i = 0; i < data.size(); i++) {
			if(data.get(i) == null){
				continue;
			}
			if (!getColumnType(i).isAssignableFrom(data.get(i).getClass())) {
				throw new IllegalArgumentException("Parameter number " + i + " is not of type "
						+ columnTypes.get(i) + " as expected.");
			}
		}
		statisticsData.add(data);
	}
	
	public void printTable(){
		TextTable tt = null;
		if(isHierarchical){
			tt = new TextTreeTable(new StatisticsTableModel());
			// TODO: fix the 'Total' in the middle when sorting
			//tt.setSort(0);
		}
		else {
			tt = new TextTable(new StatisticsTableModel());
		}
		tt.setAddRowNumbering(true);
		tt.printTable();
	}
	
	private class StatisticsTableModel extends TextTableModel {

		private static final long serialVersionUID = 1L;
		
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if (rowIndex == getStatisticsData().size()) {
				if(columnIndex == 0){
					return "Total";
				}
				if (isNumeric(columnIndex))
					return getTotal(columnIndex);
				return "";
			}
			List<List<Object>> sd = getStatisticsData();
			sd.get(rowIndex);
			Object value = sd.get(rowIndex).get(columnIndex);
			return value;
		}

		@Override
		public int getRowCount() {
			return getStatisticsData().size() + 1;
		}

		@Override
		public String getColumnName(int column) {
			return getColumnTitles().get(column);
		}

		@Override
		public int getColumnCount() {
			return getColumnTitles().size();
		}

		@Override
		public boolean addSeparatorAt(int row) {
			return row == getRowCount()-1;
		}

		@Override
		public boolean allowNumberingAt(int row) {
			return row != getRowCount()-1;
		}
	}
}
